fix(a11y): announce workflow / event status on timeline graph nodes (WCAG 1.4.1)#3443
Conversation
…WCAG 1.4.1) Adds aria-label to the SVG <g role="button"> wrapper on each timeline-graph node so screen readers announce the workflow id and status (or event type and classification) instead of bare "button". Previously, terminal statuses on the timeline graph (Completed, Failed, Canceled, TimedOut, Fired, Signaled) were distinguishable only by node color. The color legend lives inside a Tooltip -- keyboard- and AT-reachable since PR #3429, but still requires the user to leave the graph, decode the color, and come back to identify each node. This change makes the graph self-describing. Cross-cutting wins: - Closes the SC 4.1.2 missing-accessible-name defect on the same <g role="button"> widgets. - Closes the SC 1.3.1 matrix-row item d deferred to the Robust wave. New i18n keys: - workflows.row-accessible-name = "Workflow {{workflowId}}: {{status}}" - events.row-accessible-name = "Event {{eventType}}: {{classification}}" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
CI's check-types caught two TS errors in the timeline-graph
aria-label additions:
1. translate(\`workflows.\${workflowStatusKey(status)}\`) -- the
helper's return type widened to plain string, which doesn't
satisfy the strongly-typed translation-key union.
2. translate(\`events.event-classification.\${classification.toLowerCase()}\`)
-- same shape; toLowerCase() returns plain string.
3. event.eventType -- doesn't exist on PendingActivity (a union
member of WorkflowEventWithPending).
Replaced both dynamic template-literal lookups with `as const`
record literals keyed by the literal-union enum values, so
TypeScript can narrow to the exact translation-key union. Added
an `in` operator narrowing for the eventType access.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EventClassification union doesn't include 'Retrying' -- only the i18n key exists. The lookup entry was unreachable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously the aria-label for a pending activity always said
"Pending" regardless of attempt count. The timeline already
communicates retry state visually (line styling), in the legend
("Retry" entry), and in the group-details row text -- but the
per-event aria-label missed it.
Check group.pendingActivity.attempt > 1 (matching the existing
group-details-row.svelte logic) and surface "Retrying" in the
aria-label when applicable. The events.event-classification.retrying
translation key already existed for this purpose.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The status/classification -> translated-label mapping existed inline in workflow-status.svelte and was duplicated by the two new lookup tables added for the timeline-graph aria-labels (workflow-row, history-graph-row-visual). Extract the canonical map into $lib/utilities/get-status-label and reuse it in all three call sites so there is a single source of truth. No behavior change: the helper map is a verbatim copy of the canonical Record<Status, string>; the history graph normalizes its lowercase 'pending'/'retrying' values to the PascalCase Status union for lookup while leaving `classification` untouched for styling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
timeline-graph-row's <g role="button"> contained only SVG primitives and <Text> (whose accessible-name contribution is unreliable across screen readers), so it announced as bare "button" — the same defect this PR fixes on workflow-row and history-graph-row-visual. Add an aria-label via the shared getStatusLabel helper. group-details-row's <g role="button"> is intentionally left for a separate fix: it wraps foreignObject HTML (already name-contributing) and nested interactive elements, so it needs a nested-interactive restructure rather than an aria-label that would mask its content. A11y-Audit-Ref: 1.4.1-timeline-graph-status Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Unit test for the shared getStatusLabel helper (workflow statuses, event
classifications, pending/retrying, and the Unknown fallback).
- Integration test asserting the timeline graph's SVG <g role="button">
nodes expose accessible names: the workflow node ("Workflow <id>: Running")
and event nodes ("Event LongActivity: Scheduled", "Event customSignal:
Signaled"). Verified red — the assertions fail if the aria-label regresses.
history-graph-row-visual shares the same getStatusLabel +
events.row-accessible-name mechanism (covered by the unit test and the
identical timeline-graph-row path); its /history graph view did not render
nodes deterministically in the integration harness.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Avoid collisions between workflow statuses and event classifications that share a name (Running, Completed, Failed, Canceled, Terminated, TimedOut). The previous single combined map silently merged them and routed event nodes through the workflows.* namespace, so a future divergence between workflows.completed and events.event-classification.completed would have mislabeled event nodes. Now expose two domain-scoped resolvers, each backed by an exhaustive Record over its own union (so a new status in either domain forces a label): - getWorkflowStatusLabel -> workflows.* (workflow-row) - getEventClassificationLabel -> events.event-classification.* (history-graph-row-visual, timeline-graph-row) getStatusLabel remains as the polymorphic resolver for WorkflowStatus.svelte, which renders one badge for any status type and cannot know the domain from the value; it prefers the workflow label on shared names, preserving that component's existing behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Summary
Adds
aria-labelto each SVG<g role="button">wrapper on the timeline-graph nodes so screen readers announce the workflow's id and status (or the event's type and classification) instead of bare "button".Previously, terminal statuses on the timeline graph (Completed, Failed, Canceled, TimedOut, Fired, Signaled) were distinguishable only by node color. The legend that decodes those colors lives inside a
<Tooltip>— keyboard- and AT-reachable since PR #3429, but still requires the user to leave the graph, decode the color in the legend, and come back to identify each node. This change makes the graph self-describing at the node level.Audit context
audit-output/issues/1.4.1-timeline-graph-status.md(this is Option A — the recommended fix).Cross-cutting wins
<g role="button">widgets.New i18n keys
workflows.row-accessible-name:"Workflow {{workflowId}}: {{status}}"events.row-accessible-name:"Event {{eventType}}: {{classification}}"Test plan
"Workflow {id}: {status}, button"instead of just"button"."Event {eventType}: {classification}, button".<g>has the expectedaria-labelattribute.svg-img-altrule should no longer flag the timeline graph rows.🤖 Generated with Claude Code
A11y-Audit-Ref: 1.4.1-timeline-graph-status